package com.sky.aspect;

/**
 * @ProjectName: sky-take-out
 * @Package: com.sky.annotation
 * @ClassName: GlobalCacheAopAround
 * @Author: 王加其
 * @Date: 2023/4/23 20:28
 * @Version: 1.0
 */

import com.sky.annotation.Cache;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.time.Duration;

/**
 * 全局缓存环绕通知
 */
@Component
@Aspect
@Slf4j
public class GlobalCacheAopAround {
    @Autowired
    private RedisTemplate redisTemplate;

    //指定切入点
    @Pointcut("@annotation(com.sky.annotation.Cache)")
    public void doAspect() {
    }

    //环绕通知
    @Around("doAspect() && @annotation(cache)")
    public Object around(ProceedingJoinPoint joinPoint, Cache cache) {
        //目标方法参数
        Object[] args = joinPoint.getArgs();
        //目标方法返回值
        Object object = null;
        try {
            //构建缓存key
            String cacheKey = getCacheKey(cache, joinPoint);
            //缓存命中
            object = this.redisTemplate.opsForValue().get(cacheKey);
            if (ObjectUtils.isEmpty(object)) {
                //缓存未命中，放行
                object = joinPoint.proceed(args);
                if (null == object) {
                    return object;
                }
            } else {
                //缓存命中，返回数据，截断请求
                return object;
            }
            //以上代码为目标方法返回前增强
            //------------------------------------------------------------------------------------------------
            //以下代码为目标方法返回后增强
            this.redisTemplate.opsForValue().set(cacheKey, object, Duration.ofSeconds(Long.valueOf(cache.time())));
        } catch (Throwable e) {
            log.error("全局缓存AOP异常：{}", e);
            e.printStackTrace();
        }
        return object;
    }

    /*
     * 获取缓存 key
     * @param cache
     * @return
     * @throws Exception
     */
    public static String getCacheKey(Cache cache, ProceedingJoinPoint joinPoint) throws Exception {
        //解析SpringEL获取动态参数
        StringBuffer cacheKeyBuffer = new StringBuffer();
        String cacheKey;
        boolean isHaveGroup = false;
        //如果指定了缓存组
        if (StringUtils.isNotEmpty(cache.group())) {
            cacheKeyBuffer.append(cache.group());
            isHaveGroup = true;
        }
        //如果指定了缓存key
        if (StringUtils.isNotEmpty(cache.key())) {
            //如果指定了缓存key
            if (isHaveGroup) {
                cacheKeyBuffer.append(":");
            }
            //解析SpringEL获取动态参数
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            String[] expressionArr = cache.key().split(",");
            for (String expression : expressionArr) {
                String keyValue = parse(expression, signature.getParameterNames(), joinPoint.getArgs());
                if (StringUtils.isNotEmpty(keyValue)) {
                    cacheKeyBuffer.append(keyValue).append("_");
                }
            }
        }
        cacheKey = cacheKeyBuffer.toString();
        if (cacheKey.endsWith("_")) {
            cacheKey = cacheKey.substring(0, cacheKey.lastIndexOf("_"));
        }
        return cacheKey;
    }

    /**
     * Spring EL表达式解析
     *
     * @param expression  EL表达式
     * @param paramNames  目标方法列表参数 属性名集合
     * @param paramValues 目标方法列表参数 属性值结合
     * @return
     */
    private static String parse(String expression, String[] paramNames, Object[] paramValues) {
        if (StringUtils.isEmpty(expression)) {
            return "";
        }
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paramNames.length; i++) {
            context.setVariable(paramNames[i], paramValues[i]);
        }
        Expression exp = new SpelExpressionParser().parseExpression(expression);
        Object value = exp.getValue(context);
        return value == null ? "" : value.toString();
    }
}
